﻿#region license
// Copyright (c) 2007 Rodrigo B. de Oliveira (rbo@acm.org)
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// 
//     * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//     * Neither the name of Rodrigo B. de Oliveira nor the names of its
//     contributors may be used to endorse or promote products derived from this
//     software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# Taken and modified from ConcurrentBoo

# This file is part of Grawlix.
# 
# Grawlix is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# 
# Grawlix is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public
# License along with Grawlix.  If not, see
# <http://www.gnu.org/licenses/>.
#endregion

"""Execute in a dedicated thread."""

namespace ConcurrentBoo.Executors
import ConcurrentBoo
import ConcurrentBoo.Primitive
import System
import System.Threading

transient abstract class ThreadedExecutor(IExecutor):
"""
Base class for executors that dispatch actions to
a single dedicated thread.
"""
	
	protected def constructor():
	"""Create a new executor using a new channel."""
		self(Channel[of Action]())
		
	protected def constructor([required] channel as IChannel[of Action]):
	"""Create a new executor using a given channel."""
		_channel = channel
	
	public event Error as EventHandler[of ErrorEventArgs]
	"""Event raised upon exception."""
	
	protected virtual def OnError(e as ErrorEventArgs):
	"""
	Custom response to exceptions.
	Param e: Arguments to the error.
	"""
		Error(self, e)
	
	def Execute([required] action as Action):
	"""
	Start executing an action.
	Param action: The action to execute.
	"""
		lock self:
			EnsureThreadStarted()
			if _thread is Thread.CurrentThread:
				ProcessAction(action)
				return
		
		_channel.Enqueue(action)
	
	def Shutdown():
	"""Wait until the queue is empty, and join the thread."""
		Shutdown(Timeout.Infinite)
		
	def Shutdown(millisecondsTimeout as int):
	"""
	Wait until the queue is empty and join the thread.
	Param millisecondsTimeout: The maximum amount of time to wait.
	Returns: true upon success, false if timed out.
	"""
		lock self:
			thread = _thread
		if thread is null: return
		assert thread is not Thread.CurrentThread
		_channel.Enqueue(EndThread)
		return _thread.Join(millisecondsTimeout)
		
	[lock]
	private def ClearThread():
	"""Wipe the thread and wake all."""
		_thread = null
		Monitor.PulseAll(self)
	
	private def EnsureThreadStarted():
	"""Start the thread if it isn't."""
		if _thread is not null: return
		_thread = CreateThread()
		_thread.Start()
		
	protected virtual def CreateThread():
	"""
	Create the thread to use.
	Returns: The new thread.
	"""
		return Thread(EntryPoint, IsBackground: true)
		
	protected def EntryPoint():
	"""Default entry point for a new thread."""
		try:
			ExecutionLoop()
		ensure:
			ClearThread()

	protected def ProcessNextMessage() as bool:
	"""
	Execute the next waiting action, waiting indefinitely.
	Returns: true upon success, false if thread should end.
	"""
		return ProcessMessage(_channel.Dequeue())
		
	protected def ProcessMessage(message) as bool:
	"""
	Execute a single action.
	Param message: An action or EndThread.
	Returns: true upon success, false if message is EndThread.
	"""
		if message is EndThread:
			return false		
		return ProcessAction(message)
		
	protected def ProcessAction(action as Action):
	"""
	Execute a single action.
	Param action: The action to execute.
	Returns: true.
	"""
		try:
			action()
		except exception as Exception:
			OnError(ErrorEventArgs(exception))
		return true
			
	protected abstract def ExecutionLoop():
	"""The main logic of the thread."""
		pass
		
	private def EndThread():
	"""An indication that the thread should die."""
		pass
	
	[getter(Channel, Protected: true)]
	private final _channel as IChannel[of Action]
	"""The channel used to queue actions."""

	private _thread as Thread
	"""The thread this executor wraps."""
	
